package de.kardass.jannobatch.manager;

import static de.kardass.jannobatch.manager.ReflectUtil.*;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import de.kardass.jannobatch.ItemProcessor;


public class BatchWorkflowValidator {
	
	private final BatchWorkflow batchWorkflow;

	public BatchWorkflowValidator(BatchWorkflow batchWorkflow) {
		this.batchWorkflow = batchWorkflow;
	}
	
	public static void validate(BatchWorkflow batchWorkflow) {
		new BatchWorkflowValidator(batchWorkflow).validate();
	}

	public void validate() {
		try {
			Class<?> previousMethodReturnType = null;
			if (batchWorkflow.itemReaderExists()) {
				previousMethodReturnType = getFirstGenericTypeOfClass(batchWorkflow.getItemReader().getClass());
			}
			if (batchWorkflow.itemProcessorExists()) {
				if (previousMethodReturnType == null) {
					previousMethodReturnType = 
						getFirstGenericTypeOfClass(batchWorkflow.getItemProcessors().get(0).getClass());
				} else {
					previousMethodReturnType = checkStepsWorkflow(0, previousMethodReturnType);
				}
			}
			if (batchWorkflow.itemWriterExists()) {
				if (previousMethodReturnType == null) {
					// only writer exists
				} else {
					Method writeMethod = getWriteMethodWithParameter(batchWorkflow.getItemWriter().getClass(), previousMethodReturnType);
					if (writeMethod == null) {
						throw new IllegalArgumentException("In-parameter of step [" + batchWorkflow.getItemWriter().getClass().getName() 
								+ "] does not match interface contract with parameter " + previousMethodReturnType.getName());
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Returns the 'typeNo' genric class of the given 'clazz'.
	 * @param clazz
	 * @param typeNo
	 * @return
	 */
	private Class<?> getFirstGenericTypeOfClass(Class<?> clazz) {
		final int typeNo = 0;
		Class<?> result = null;
		Type[] types = clazz.getGenericInterfaces();
		Type superClassType = clazz.getGenericSuperclass();
		
		if (types.length == 0 && superClassType == null) {
			result = getFirstGenericTypeOfClass(clazz.getSuperclass());
		} else if (types.length > 0) {
			types = ((ParameterizedType) types[0]).getActualTypeArguments();
			result = (Class<?>) types[typeNo];
		} else if (superClassType != null) {
			types = ((ParameterizedType) superClassType).getActualTypeArguments();
			result = (Class<?>) types[typeNo];
		} else {
			throw new IllegalStateException("Unknown generic type of " + clazz);
		}
		return result;
	}

	/**
	 * Checks whether the contract (parameters) match: previous step's return type
	 * or one super type must be equal to the current step's (position) 
	 * input parameter. 
	 * @param position
	 * @param previousReturnType
	 * @return
	 * @throws Exception
	 */
	private Class<?> checkStepsWorkflow(int position, Class<?> previousReturnType) throws Exception {
		if (position < batchWorkflow.getItemProcessors().size()) {
			final ItemProcessor<?, ?> itemProcessor = batchWorkflow.getItemProcessors().get(position);
			// process method exists with previous return parameter type
			final Method processMethod = getProcessMethodWithParameter(itemProcessor.getClass(), previousReturnType);
			if (processMethod == null) {
				throw new IllegalArgumentException("In-parameter of step [" + itemProcessor.getClass().getName() 
						+ "] does not match interface contract with parameter " + previousReturnType.getName());
			}
			++position;
			return checkStepsWorkflow(position, processMethod.getReturnType());
		} else {
			return previousReturnType;
		}
	}

//	private boolean processMethodExistsWithParameter(Class<?> processorClass, Class<?> processMethodParameter) {
//		return getProcessMethodWithParameter(processorClass, processMethodParameter) != null;
//    }

//	private void checkParameterMatching(Class<?> returnType, Class<?> inType) {
//		if (!returnType.equals(inType) && !isSuperClassOf(returnType, inType)) {
//			throw new IllegalArgumentException("Return type previous step [" + returnType.getName() 
//					+ "] does not match in-parameter [" + inType.getName() + "]");
//		}
//	}

//	private Method getProcessMethod(Class<?> clazz) {
//		return getMethod(clazz, PROCESS);
//	}
	
//	private Class<?> getProcessMethodParameter(Class<?> clazz) {
//		return getAndAssertSingleMethodParameter(getProcessMethod(clazz));
//	}

//	private Class<?> getAndAssertSingleMethodParameter(Method method) {
//		Class<?> result = null;
//		if (method != null) {
//			if (method.getParameterTypes().length != 1) {
//				throw new IllegalArgumentException("Only 1 parameter expected of type for " + method);
//			}
//			result = method.getParameterTypes()[0];
//		}
//		return result;
//	}
	
//	private Class<?> getProcessMethodReturnType(Class<?> clazz) {
//		Class<?> result = null;
//		Method method = getProcessMethod(clazz);
//		if (method != null) {
//			result = method.getReturnType();
//		}
//		return result;
//	}

//	private Method getMethod(Class<?> clazz, String methodName) {
//		Method result = null;
//		final Method[] methods = clazz.getMethods();
//		for (int i = 0; i < methods.length; i++) {
//			if (methods[i].getName().equals(methodName)) {
//				if (result == null) {
//					result = methods[i];
//				} else {
//					Class<?> returnType = result.getParameterTypes()[0];
//					if (Object.class.equals(returnType)) {
//						result = methods[i];
//					}
//				}
//			}
//		}
//		return result;
//	}

}
